#include "system.h"
#include <iostream>
#include <fstream>
#include "tuxdocument.h" // class's header file
#include "baskets.h"
#include "winassert.h"
#include "filesys.h"

using sys::runtime_error_ex;
using std::ifstream;
using std::string;


namespace doc
{
 	const string DocNode::NO_NAME = "(none)";
 	const char * TXT_UNTITLED = "untitled";
 	const char * TXT_TEMPLATE = "TuxPadTemplate";

	int incrementor;
	using sixml::Node;
	using design::aggregate_ptr_list;
 	TuxDocument theDocument;  /// the one and only!
	
	void Attribute::SetValue(string &v)
	{
		if (_type.AcceptValue(v))
		{
			if (v == _value) 
				return;
		    _value = v;
		    _owner.Invalidate(this);
  		}
 		else
 			throw runtime_error_ex("Cannot accept value '%s' for '%s'",
    				v.c_str(),_type.Name().c_str());
	}
	
	Attribute::Attribute(DocNode& owner, const Property &p, string & v)
	: _owner(owner), _type(p)
	{
		SetValue(v);
	}

	Attribute::Attribute(DocNode& owner, const Attribute& source)
	: _owner(owner), _type(source._type)
	{
		_value = source._value;
	}


	void DocNode::Copy(const DocNode& source)
	{
		Destroy();
		_pStructure = source._pStructure;
		_text = source._text;
		_childs.clone_from(source._childs);
		pointer_list<DocNode>::iterator itc = _childs.begin();
  		while (itc != _childs.end())
    	{
    		(*itc)->_pParent = this;
    		itc++;
     	} 
		// create new attributes
		_attribs.clear();
		pointer_list<Attribute>::const_iterator it = source._attribs.begin();
		while (it != source._attribs.end())
		{
			_attribs.push_back(new Attribute(*this,*(*it)));
			it++;
		}
	}

	DocNode::DocNode(const DocNode& source)
	: _owner(source._owner)
	{
		_pStructure = 0;
		_pParent = 0;
		Copy(source);
	}
	
	void DocNode::GetNameLabel(string& result) const
 	{
 		result = "";
		if (NodeStructure().IsNamed())
			result = Name();
		else
			result = NodeStructure().Name();

		string secname =  SecondName();
		if (secname != DocNode::NO_NAME) 
			result += " (" + secname + ")";
 	}
 	
	void DocNode::AppendNodeNames(const char * path, 
 					list<string> & result) const
	{
		const char * p = path;
		while (*p == '\\' || *p == '/' || *p == ' ')
			p++;
		if (*p == 0)		
		{
			return;
		}
		else
		{
			// Get the name of the node structure to traverse
			string filter;
			while (*p != '\\' && *p != '/' && *p != 0)
			{
				filter += *p;
				p++;
			}
			
			// Find and traverse child nodes
			list<DocNode*> trav_list;
			AppendChildsByFilter(filter,trav_list);
			list<DocNode*>::const_iterator it = trav_list.begin();
			while (it != trav_list.end())
			{
				if (*p == 0)
					result.push_back((*it)->Name());
				else
					(*it)->AppendNodeNames(p,result);
				it++;
			}	
		}
	}

	void DocNode::Invalidate(Attribute * pAttrib)
 	{
 		_owner.Invalidate();
		_owner.InvalidateName(this);
 	}
 	
	bool DocNode::CanMove(const eMoveMethod m) const
	{
		if (!_pParent)
			return false;
		if (m == mmUp || m == mmTop)
  			return *_pParent->Childs().begin() != this;	
  		if (m == mmDown || m == mmBottom)
  		{
  			DocNode * last = _pParent->Childs().back();
    		return last != this;	
		}
		return false;
	}
	
	void DocNode::AddChild(DocNode * p)
	{ 
		_childs.push_back(p); 
		p->_pParent = this;
		_owner.Invalidate();
	}

	void DocNode::RemoveChild(DocNode * p)
	{ 
		_childs.remove(p);
		p->_pParent = 0;
		_owner.Invalidate();
	}

	bool DocNode::CanAddChild(DocNode * p)
	{ 
		pointer_list<const Structure> addables;
		GetAddableStructures(addables);
		const Structure * ps = (p->_pStructure);
		return (addables.find(ps) != addables.end());
	}
	
	void DocNode::Move(const eMoveMethod m)
	{
		ASSERT(CanMove(m));
		list<DocNode*>::iterator it = _pParent->_childs.find(this);
		list<DocNode*>::iterator itFrom = it;
   			ASSERT(*itFrom == this);
		list<DocNode*>::iterator itTo;
		if (m == mmUp)
			itTo = --it;
		else if (m == mmDown)
			itTo = ++it;
		else if (m == mmTop)
		{
			while (CanMove(mmUp))
				Move(mmUp);
			return;	
	    }
		else  if (m == mmBottom)
		{
			while (CanMove(mmDown))
				Move(mmDown);
			return;	
	    }
   		_owner.Invalidate();     
		*itFrom = *itTo;
   		*itTo = this;	
	}

	void DocNode::Destroy()	
	{
		// first delete childs
		pointer_list<DocNode>::iterator it = _childs.begin();
		while (it != _childs.end())
		{
			(*it)->_pParent = 0;
			delete *it;
			it++;
		}
		// remove from parent
		if (_pParent)
			_pParent->_childs.remove(this);
		_pParent = 0;	
	}
	
	DocNode::~DocNode()
	{
		Destroy();
	}
	
	
	bool DocNode::CanDelete() const
	{
		if (!_pParent)
			return false;
		if (_pStructure->IsOptional()) // 0-X
			return true;
		if (_pStructure->IsOne()) // 1-1
			return false;
		// do we have one of many? 1-m
		pointer_list<DocNode> & siblings = _pParent->_childs;
		pointer_list<DocNode>::iterator it = siblings.begin();
		bool found = false;
		while (it != siblings.end() && !found)
		{
			found =
				(*it) != this &&
   				(*it)->_pStructure == _pStructure;
			it++;
		}
		return found;
	}
	
	DocNode::DocNode(TuxDocument &owner, DocNode* parent, const Structure& s)
	: _owner(owner), _pStructure(&s)
	{
		_pParent = parent;
    	Initialise();
	}
	
	DocNode::DocNode(TuxDocument &owner, DocNode* parent, const Structure& s, const sixml::Node& n)
	: _owner(owner),_pStructure(&s)
	{
		_pParent = parent;
		// verify
		if (s.Name() != n.Name())
			throw runtime_error("Structure and Node does not match.");
		// load
		_text = n.Value();
		InitAttribs();
		pointer_list<Attribute>::iterator ait = _attribs.begin();
		while (ait != _attribs.end())
		{
			Attribute * pA = *ait;
			const string& attribName = pA->Type().Name();
			if (n.HasAttribute(attribName))
			{
				string attribValue = n.Attribute(attribName);
      			pA->SetValue(attribValue);
      		}
			ait++;
		}
		Node::const_iterator cit = n.begin();
		while (cit != n.end())
		{
        	const Structure& child = _pStructure->ChildByName((*cit)->Name());
        	_childs.push_back(new DocNode(_owner,this,child,*(*cit)));
			cit++;
		}
		
		// Add mandatory child not in doc
		StructurePtrList mands;
		_pStructure->GetMandatoryChilds(mands);
  		StructurePtrList::const_iterator it = mands.begin();
    	while (it != mands.end())
    	{
    		if (!HasChildWithStructure(*(*it)))
    			_childs.push_back(new DocNode(_owner,this,*(*it)));
    		it++;
    	}
    	

	}

 	Attribute & DocNode::AttribByName(const string& name) const
 	{
   		aggregate_ptr_list<Attribute>::const_iterator it = _attribs.begin();
     	while (it != _attribs.end())
     	{
     		if ((*it)->Type().Name() == name)
     			return *(*it);
     		it++;
     	}
     	throw runtime_error_ex("Attribute with name '%s' not found",
      			name.c_str());
 	}

 	void DocNode::AppendChildsByFilter(const string& filter, 
  			list<DocNode*> & result) const
 	{
		/// if we have a '=' in the filter, the value after the '='
		/// is taken to be the docnode name filter
		/// if the name starts with a space, the structure name is ignored
		const int delim_pos = filter.find('=');
		string node_name;
		string struct_name = filter;
		const bool find_structure_name = delim_pos != 0;
		const bool find_doc_name = delim_pos > -1;
		if (find_doc_name)
		{
			node_name = filter.substr(delim_pos+1);
			struct_name = filter.substr(0,delim_pos);
		}
   		list<DocNode*>::const_iterator it = _childs.begin();
     	while (it != _childs.end())
     	{
     		DocNode * at = *it;
     		bool found = true;
     		if (find_structure_name)
			{
				if (at->NodeStructure().Name() != struct_name)
					found = false;
			};
			if (found && find_doc_name)
			{
				if (at->Name() != node_name)
					found = false;
			}
			if (found)
   				result.push_back(at);
     		it++;
     	}
 	}
 	
	const string& DocNode::Name() const
	{
		if (_pStructure->IsNamed())
			return (*_attribs.begin())->Value();
		else
			return DocNode::NO_NAME;
	}


	const string& DocNode::SecondName() const
	{
		Property * p = _pStructure->SecondNameProperty();
		if ( p != 0)
			return AttribByName(p->Name()).Value();
		else
			return DocNode::NO_NAME;
	}

	void DocNode::InitAttribs()
	{
	    // handle name
    	if (_pStructure->IsNamed())
    	{
   			Property * nameProp = _pStructure->NameProperty();
   			if (!nameProp)
   				throw runtime_error_ex(
       				"Name type expected for '%s'.",_pStructure->Name().c_str());
       		string defname = "unnamed";
         	_attribs.push_back(new Attribute(*this,*nameProp,defname));
		}

		// create attributes
		const pointer_list<Property> &props = _pStructure->Properties();
		pointer_list<Property>::const_iterator pit = props.begin();
		Property * p;
		while (pit != props.end())
		{
			p = *pit;
			string v = p->Default();
			_attribs.push_back(new Attribute(*this,*p,v));
			pit++;
		}
	}
	void DocNode::Initialise()
	{
		// add all mandatory children
		StructurePtrList mands;
		_pStructure->GetMandatoryChilds(mands);
  		StructurePtrList::const_iterator it = mands.begin();
    	while (it != mands.end())
    	{
      		_childs.push_back(new DocNode(_owner,this,*(*it)));
    		it++;
    	}
   		InitAttribs();
	    // handle name
    	if (_pStructure->IsNamed())
    	{
	    	string name = _pStructure->Name();
	    	char buffer[10];
			::itoa(++incrementor,buffer,10);
			name += "_";
			name += buffer;
			(*_attribs.begin())->SetValue(name);
		}

	}

	
	const DocNode& TuxDocument::RootNode() const
	{
		ASSERT(_pRootNode);
		return *_pRootNode;
	}

	void DocNode::GetLabel(string & result) const
	{
		result = _pStructure->Name();
		if (_pStructure->IsNamed())
		{
			result += " - ";
			result += Name();
		}
	}
	
	bool DocNode::HasChildWithStructure(const Structure& s) const
	{
		pointer_list<DocNode>::const_iterator it = _childs.begin();
		while (it != _childs.end())
			if ((*it)->_pStructure == &s)
				return true;
			else
				it++;
		return false;
	}
	
	void DocNode::GetAddableStructures(pointer_list<const Structure> &r) const
	{
		const pointer_list<Structure> &sc = _pStructure->Childs();
  		pointer_list<Structure>::const_iterator it = sc.begin();
    	const Structure *s;
    	while (it != sc.end())
    	{	
    		s = *it;
    		if (s->IsOptional()) // 0..X
    		{
    			if (s->IsOne()) // 0..1
    			{
    				if (!HasChildWithStructure(*s))
    					r.push_back(s);
    			} else  // 0..M
    				r.push_back(s);
    		}
    		else // 1..X
    		{
    			if (s->IsMany()) // 1..M 
    				r.push_back(s);
    		}
    		it++;
    	}
	}
	
	void DocNode::SetText(const char * value)
	{
		if (_text == value)
			return;
		else {	
			_text = value;
			const char * t = _text.c_str();
			_owner.Invalidate();
			
		}
	}
	
	void DocNode::SaveToNode(Node &n)
	{
		n.SetName(_pStructure->Name());
		n.SetValue(_text);
		
		// attributes
    	pointer_list<Attribute>::iterator ait = _attribs.begin();
    	Attribute * pa;
    	while (ait != _attribs.end())
    	{
      		pa = *ait;
      		n.SetAttribute(pa->Type().Name(),pa->Value());
    		ait++;
    	}
    	
    	// child nodes
    	pointer_list<DocNode>::iterator cit = _childs.begin();
     	while (cit != _childs.end())
     	{
     		Node* cn = new Node();
     		(*cit)->SaveToNode(*cn);
     		n.AddChild(*cn);
       		cit++;
     	}
	}
	

/******************************************************************************/

    void TuxDocument::CutFocus()
    {
		ASSERT(_pFocusNode);
		ASSERT(_pFocusNode->CanDelete());
    	if (_pCloneSource)
		{
			delete _pCloneSource;
			_pCloneSource = 0;
		}	
		_cloneCopy = false;
		DocNode * parent = _pFocusNode->Parent();
  		parent->RemoveChild(_pFocusNode);
  		_pCloneSource = _pFocusNode;
		SetFocusNode(parent);
		Invalidate();
		Notify(ASPECT_CLONE_UPDATE);
    }
    
    void TuxDocument::CopyFocus()
    {
		ASSERT(_pFocusNode);
    	if (_pCloneSource)
		{
			delete _pCloneSource;
			_pCloneSource = 0;
		}	
		Notify(ASPECT_FOCUS_SAVE);
  		_cloneCopy = true;
  		_pCloneSource = new DocNode(*_pFocusNode);
		Notify(ASPECT_CLONE_UPDATE);
    }
    
    void TuxDocument::CloneToFocus()
    {
    	ASSERT(_pCloneSource);
    	if (!CanCloneToFocus())
    		throw runtime_error("Cannot clone to focus node");
    	if (_cloneCopy)
    		_pFocusNode->AddChild(new DocNode(*_pCloneSource));
	    else			
	    {
	    	_pFocusNode->AddChild(_pCloneSource);
    		_pCloneSource = 0;	
			Notify(ASPECT_CLONE_UPDATE);
		}
    }
    
    bool TuxDocument::CanCloneToFocus() const
    {
    	if (_pCloneSource == 0 || _pFocusNode == 0)
    		return false;
    	return _pFocusNode->CanAddChild(_pCloneSource);	
    		
    }

    bool TuxDocument::HasCutCloneSource() const
    {
    	return _pCloneSource != 0 && !_cloneCopy;
    }

  	TuxDocument::TuxDocument()
	{
		_filename = TXT_UNTITLED;
		_pRootNode = 0;
		_pFocusNode = 0;
		_isDirty = false;
		_cloneCopy = false;
		_pCloneSource = 0;
	}

	/* TODO (#9#): Prepareforclose is a bit of a hack until more "realtime" 
	               notification of updates can occur */
	void TuxDocument::PrepareForClose()
	{
		if (_pFocusNode)
		{
			Notify(ASPECT_FOCUS_SAVE);
		}
	}
	
	void TuxDocument::CloseDocument()
	{
		if (_pFocusNode)
		{
			Notify(ASPECT_FOCUS_CLOSE);
			_pFocusNode = 0;
		}
		if (_pRootNode)
		{
			delete _pRootNode;
			_pRootNode = 0;
		}
		_filename = TXT_UNTITLED;
		SetIsDirty(false);
	}
	
	// class destructor
	TuxDocument::~TuxDocument()
	{
	  delete _pRootNode;
	}

	void TuxDocument::LoadFromFile(const string& file_name)
	{
		ASSERT(_pRootNode == 0); // no file currently open
		Node xmlDoc;
		xmlDoc.ReadFromFile(file_name.c_str());
		// now make a backup of the existing file
		sys::File f(file_name);
		f.Backup();
		
		// verify document
		if (!xmlDoc.HasAttribute(TXT_TEMPLATE))
  			throw runtime_error_ex(
     			"Not a valid TuxPad document. Expected attribute '%s'",TXT_TEMPLATE);

     	// load template
		const string templateFilename
  				= Prefs.GetTemplateFilename(xmlDoc.Attribute(TXT_TEMPLATE));		
  		try
    	{		
	  		_template.Load(templateFilename);
  			if (xmlDoc.Name() != _template.structure().Name())
  			{
  				throw runtime_error("TuxPad document and template do not match.");
  			}
  			_pRootNode = new DocNode(*this,0, _template.structure(),
    								 xmlDoc);
			_filename = file_name;
			SetIsDirty(false);
			Notify(ASPECT_FILE);
		}
  		catch (...)
    	{	
    		delete _pRootNode;
    		_pRootNode = 0;
			Notify(ASPECT_FILE);
			throw;
		}
	}

    void TuxDocument::SaveToFile(const char * fn )
    {
    	_filename = fn;
    	SaveToFile();
    }

    void TuxDocument::SaveToFile()
    {
    	if (IsNodeSelected())
			Notify(ASPECT_FOCUS_SAVE);
    	Node doc;
     	_pRootNode->SaveToNode(doc);
    	doc.SetAttribute(TXT_TEMPLATE,_template.name());
     	std::ofstream file(_filename.c_str());
     	if (!file)
     		throw runtime_error_ex("Could not save to file '%s'",_filename.c_str());

		file << "<?xml-stylesheet href=\""
  			 << Prefs.TemplatePath() << _template.name().c_str()
	     	 << ".xslt\" type=\"text/xsl\"?>"; 
      	doc.WriteToStream(file); 
      	file.flush();
      	SetIsDirty(false);
    }

    void TuxDocument::SetIsDirty(const bool value)
    {
    	if (value != _isDirty)
    	{
    		_isDirty = value;
    		Notify(ASPECT_DIRTY_UPDATE);
    	}
    }
    
 	void TuxDocument::Invalidate()
 	{
   		SetIsDirty(true);
   	}
   	
   	void TuxDocument::InvalidateName(DocNode * pNode)
 	{
   		if (pNode == _pFocusNode)
   			Notify(ASPECT_FOCUS_NAME_UPDATE);
   	}
   	
	bool TuxDocument::IsDirty() const
	{
  		return _isDirty;
  		/* DONE (#1#): More practical dirty file management */
  		
	}
	
	bool TuxDocument::HasFileName()
	{
		return _filename != TXT_UNTITLED;
	}

	void TuxDocument::NewFromTemplate(const int templateIndex)
	{
		ASSERT(_pRootNode == 0); // no current file 
		string filename;
	    Prefs.GetTemplateFilename(templateIndex,filename);
		_template.Load(filename);
		delete _pRootNode;
  		_pRootNode = new DocNode(*this,0,_template.structure());
  		Invalidate();
		Notify(ASPECT_FILE);
	}

	void TuxDocument::SetFocusNode(DocNode * pNode)
	{
		if (_pFocusNode != pNode)
		{
			if (_pFocusNode)
			{
				Notify(ASPECT_FOCUS_SAVE);
				Notify(ASPECT_FOCUS_CLOSE);
			}
			Notify(ASPECT_FOCUS_UPDATE);
			_pFocusNode = pNode;
			if (_pFocusNode)
	   			Notify(ASPECT_FOCUS_OPEN);
		}
	}
	
	DocNode * TuxDocument::NewFocusNodeChild(const Structure &s)
	{
		ASSERT(_pFocusNode);
     	DocNode * n = new DocNode(*this,_pFocusNode,s);
     	_pFocusNode->AddChild(n);
     	return n;
	}
	

	void TuxDocument::DeleteFocusNode()
	{
		ASSERT(_pFocusNode);
		ASSERT(_pFocusNode->CanDelete());
		DocNode * parent = _pFocusNode->Parent();
		delete _pFocusNode;
		_pFocusNode = 0; // focus node not accessable anymore
		SetFocusNode(parent);
		Invalidate();
	}
	
	DocNode * TuxDocument::FocusNode()
	{
		ASSERT(_pFocusNode); // caller must check
		return _pFocusNode;
	}

	TuxDocument & TuxDocument::Instance()
 	{
	  	return	theDocument;
	}  	

}
